package com.mlethe.library.qrcode.zxing;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * 扫一扫UI
 *
 * @author Mlethe
 */
class ScanBoxView extends View {

    @IntDef({CORNER_OUTSIDE, CORNER_CENTER, CORNER_INSIDE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CornerGravity {
    }

    @IntDef({SCAN_TOP, SCAN_CENTER, SCAN_BOTTOM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScanGravity {
    }

    @IntDef({TIP_TOP, TIP_BOTTOM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface TipGravity {
    }

    /**
     * 扫描框四角线位置：边框线外面
     */
    public static final int CORNER_OUTSIDE = 0x01;
    /**
     * 扫描框四角线位置：边框线中间
     */
    public static final int CORNER_CENTER = 0x02;
    /**
     * 扫描框四角线位置：边框线里面
     */
    public static final int CORNER_INSIDE = 0x03;

    /**
     * 扫描框位置：View顶部
     */
    public static final int SCAN_TOP = 0x01;
    /**
     * 扫描框位置：View中间
     */
    public static final int SCAN_CENTER = 0x02;
    /**
     * 扫描框位置：View底部
     */
    public static final int SCAN_BOTTOM = 0x03;

    /**
     * 提示文字位置：扫描框顶部
     */
    public static final int TIP_TOP = 0x01;
    /**
     * 提示文字位置：扫描框底部
     */
    public static final int TIP_BOTTOM = 0x03;

    private Paint mPaint;
    private Matrix matrix;
    private Rect mGridRect, mGridSrcRect;
    /**
     * view的宽度、高度
     */
    private float mWidth, mHeight;
    /**
     * 扫描框的宽度、高度
     */
    private float mScanWidth, mScanHeight;
    /**
     * 扫描框的位置
     */
    private int mScanGravity;
    /**
     * 扫描框偏移 正数：向下偏移，负数：向上偏移
     */
    private float mScanOffset;
    /**
     * 扫描框边框大小
     */
    private float mScanBorderSize;
    /**
     * 扫描框四角宽度、长度
     */
    private float mScanCornerSize, mScanCornerLength;
    /**
     * 扫描框边框颜色
     */
    private int mScanBorderColor;
    /**
     * 扫描框四角颜色
     */
    private int mScanCornerColor;
    /**
     * 扫描框四角的位置
     */
    private int mScanCornerGravity;
    /**
     * 遮罩颜色
     */
    private int mMaskColor;
    /**
     * 提示文字
     */
    private String mTipText;
    /**
     * 系统提示文字
     */
    private String mTipSysText;
    /**
     * 提示文字间距
     */
    private float mLineSpacing;
    /**
     * 提示文字大小
     */
    private float mTipSize;
    /**
     * 提示文字位置
     */
    private int mTipGravity;
    /**
     * 提示文字和扫描框间距
     */
    private float mTipMargin;
    /**
     * 提示文字颜色
     */
    private int mTipColor;
    /**
     * 提示文字背景颜色
     */
    private int mTipBackgroundColor;
    /**
     * 提示文字背景圆角
     */
    private float mTipBackgroundRadius;
    /**
     * 提示文字和背景的内间距
     */
    private float mTipPaddingLeft, mTipPaddingRight, mTipPaddingTop, mTipPaddingBottom;
    /**
     * 扫描线宽度
     */
    private float mScanLineWidth;
    /**
     * 扫描线移动距离
     */
    private float mScanLineMoveDistance;
    /**
     * 扫描线图片资源id
     */
    private int mScanLineResId;
    /**
     * 扫描线图片
     */
    private Bitmap mScanLineBitmap;
    /**
     * 网格扫描线图片资源id
     */
    private int mGridScanLineResId;
    /**
     * 网格扫描线图片
     */
    private Bitmap mGridScanLineBitmap;
    /**
     * 是否使用网格图片，true 使用，false 不使用
     */
    private boolean isGridScanLine;
    /**
     * 整个动画时间
     */
    private long mAnimTime;
    /**
     * 扫描线移动距离
     */
    private float mMoveStepDistance;
    /**
     * 扫描线位置
     */
    private float mScanLineTop;
    /**
     * 动画间隔时间
     */
    private long mAnimDelayTime;

    public ScanBoxView(Context context) {
        this(context, null);
    }

    public ScanBoxView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScanBoxView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.QRCodeView);
        mScanWidth = typedArray.getDimension(R.styleable.QRCodeView_scanWidth, dp2px(220));
        mScanHeight = typedArray.getDimension(R.styleable.QRCodeView_scanHeight, dp2px(220));
        mScanGravity = typedArray.getInteger(R.styleable.QRCodeView_scanGravity, SCAN_CENTER);
        mScanOffset = typedArray.getDimension(R.styleable.QRCodeView_scanOffset, 0);
        mScanBorderSize = typedArray.getDimension(R.styleable.QRCodeView_scanBorderSize, dp2px(1));
        mScanCornerSize = typedArray.getDimension(R.styleable.QRCodeView_scanCornerSize, dp2px(3));
        mScanCornerLength = typedArray.getDimension(R.styleable.QRCodeView_scanCornerLength, dp2px(20));
        mScanBorderColor = typedArray.getColor(R.styleable.QRCodeView_scanBorderColor, Color.WHITE);
        mScanCornerColor = typedArray.getColor(R.styleable.QRCodeView_scanCornerColor, Color.WHITE);
        mScanCornerGravity = typedArray.getInteger(R.styleable.QRCodeView_scanCornerGravity, CORNER_CENTER);
        mMaskColor = typedArray.getColor(R.styleable.QRCodeView_maskColor, Color.parseColor("#33FFFFFF"));
        mTipText = typedArray.getString(R.styleable.QRCodeView_tipText);
        mLineSpacing = typedArray.getDimension(R.styleable.QRCodeView_lineSpacing, dp2px(5));
        mTipSize = typedArray.getDimension(R.styleable.QRCodeView_tipSize, sp2px(14));
        mTipGravity = typedArray.getInteger(R.styleable.QRCodeView_tipGravity, TIP_BOTTOM);
        mTipMargin = typedArray.getDimension(R.styleable.QRCodeView_tipMargin, dp2px(30));
        mTipColor = typedArray.getColor(R.styleable.QRCodeView_tipColor, Color.WHITE);
        mTipBackgroundColor = typedArray.getColor(R.styleable.QRCodeView_tipBackgroundColor, Color.parseColor("#22000000"));
        mTipBackgroundRadius = typedArray.getDimension(R.styleable.QRCodeView_tipBackgroundRadius, dp2px(5));
        mTipPaddingLeft = typedArray.getDimension(R.styleable.QRCodeView_tipPaddingLeft, dp2px(20));
        mTipPaddingRight = typedArray.getDimension(R.styleable.QRCodeView_tipPaddingRight, dp2px(20));
        mTipPaddingTop = typedArray.getDimension(R.styleable.QRCodeView_tipPaddingTop, dp2px(10));
        mTipPaddingBottom = typedArray.getDimension(R.styleable.QRCodeView_tipPaddingBottom, dp2px(10));
        mScanLineWidth = typedArray.getDimension(R.styleable.QRCodeView_scanLineWidth, dp2px(215));
        mScanLineMoveDistance = typedArray.getDimension(R.styleable.QRCodeView_scanLineMoveDistance, dp2px(220));
        mScanLineResId = typedArray.getResourceId(R.styleable.QRCodeView_scanLineResId, R.mipmap.qrcode_default_scan_line);
        mGridScanLineResId = typedArray.getResourceId(R.styleable.QRCodeView_gridScanLineResId, R.mipmap.qrcode_default_grid_scan_line);
        isGridScanLine = typedArray.getBoolean(R.styleable.QRCodeView_isGridScanLine, false);
        mAnimTime = typedArray.getInteger(R.styleable.QRCodeView_animTime, 1000);
        typedArray.recycle();
        matrix = new Matrix();
        mGridRect = new Rect();
        mGridSrcRect = new Rect();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mMoveStepDistance = dp2px(2);
        countAnimTime();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);

        // MeasureSpec.AT_MOST：在布局中指定了wrap_content；MeasureSpec.EXACTLY：在布局中指定了确切的值，如100dp，match_parent，fill_parent；MeasureSpec.UNSPECIFIED：尽可能的大，很少能用到，如ListView，ScrollView
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY) {
            mWidth = mScanWidth;
        } else if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST) {
            mHeight = mScanHeight;
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            mWidth = mScanWidth;
            mHeight = mScanHeight;
        }
        // 设置该view的宽高
        setMeasuredDimension((int) mWidth, (int) mHeight);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        createBitmap();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mScanLineBitmap != null) {
            mScanLineBitmap.recycle();
            mScanLineBitmap = null;
        }
        if (mGridScanLineBitmap != null) {
            mGridScanLineBitmap.recycle();
            mGridScanLineBitmap = null;
        }
    }

    /**
     * 创建bitmap
     */
    private void createBitmap() {
        if (isGridScanLine) {
            if (mScanLineBitmap != null) {
                mScanLineBitmap.recycle();
                mScanLineBitmap = null;
            }
            if (mGridScanLineBitmap == null) {
                mGridScanLineBitmap = createBitmap(mGridScanLineResId, mScanLineWidth);
            }
        } else {
            if (mGridScanLineBitmap != null) {
                mGridScanLineBitmap.recycle();
                mGridScanLineBitmap = null;
            }
            if (mScanLineBitmap == null) {
                mScanLineBitmap = createBitmap(mScanLineResId, mScanLineWidth);
            }
        }
    }

    /**
     * 计算动画时间
     */
    private void countAnimTime() {
        mAnimDelayTime = (long) ((1.0f * mAnimTime * mMoveStepDistance) / mScanLineMoveDistance);
    }

    /**
     * 创建绘制的图像
     *
     * @param resId
     * @param width
     * @return
     */
    private Bitmap createBitmap(int resId, float width) {
        if (resId != 0) {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
            int bitmapWidth = bitmap.getWidth();
            int bitmapHeight = bitmap.getHeight();
            matrix.reset();
            // 获取缩放比例
            float scale = 1;
            if (width > 0) {
                scale = width / bitmapWidth;
            }
            if (scale == 1) {
                return bitmap;
            }
            matrix.postScale(scale, scale);
            // 根据缩放比例获取新的位图
            Bitmap createdBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
            bitmap.recycle();
            bitmap = null;
            return createdBitmap;
        }
        return null;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        /**
         * 计算扫描框的位置
         */
        float top;
        if (mScanGravity == SCAN_TOP) {
            top = mScanOffset;
        } else if (mScanGravity == SCAN_BOTTOM) {
            top = mHeight - mScanHeight + mScanOffset;
        } else {
            top = (mHeight - mScanHeight) / 2 + mScanOffset;
        }
        float bottom = top + mScanHeight;
        float left = (mWidth - mScanWidth) / 2;
        float right = left + mScanWidth;
        /**
         * 绘制遮罩
         */
        drawMask(canvas, left, top, right, bottom);
        /**
         * 绘制扫描框
         */
        drawScanBorder(canvas, left, top, right, bottom);
        /**
         * 绘制提示文字及背景
         */
        drawTip(canvas, left, top, right, bottom);
        /**
         * 绘制扫描线
         */
        drawScanLine(canvas, left, top, right, bottom);
        /**
         * 移动扫描线
         */
        moveScanLine(canvas, left, top, right, bottom);
    }

    /**
     * 绘制遮罩
     *
     * @param canvas
     * @param left   扫描框左边坐标
     * @param top    扫描框顶部坐标
     * @param right  扫描框右边坐标
     * @param bottom 扫描框底部坐标
     */
    private void drawMask(Canvas canvas, float left, float top, float right, float bottom) {
        if (mMaskColor != Color.TRANSPARENT) {
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mMaskColor);
            canvas.drawRect(0, 0, mWidth, top, mPaint);
            canvas.drawRect(0, top, left, bottom, mPaint);
            canvas.drawRect(right, top, mWidth, bottom, mPaint);
            canvas.drawRect(0, bottom, mWidth, mHeight, mPaint);
        }
    }

    /**
     * 绘制扫描框
     *
     * @param canvas
     * @param left   扫描框左边坐标
     * @param top    扫描框顶部坐标
     * @param right  扫描框右边坐标
     * @param bottom 扫描框底部坐标
     */
    private void drawScanBorder(Canvas canvas, float left, float top, float right, float bottom) {
        if (mScanBorderColor != Color.TRANSPARENT && mScanBorderSize > 0) {
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(mScanBorderColor);
            mPaint.setStrokeWidth(mScanBorderSize);
            canvas.drawRect(left, top, right, bottom, mPaint);
        }
        if (mScanCornerColor != Color.TRANSPARENT && mScanCornerSize > 0 && mScanCornerLength > 0) {
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mScanCornerColor);
            if (mScanCornerGravity == CORNER_INSIDE) {
                canvas.drawRect(left, top, left + mScanCornerLength, top + mScanCornerSize, mPaint);
                canvas.drawRect(left, top, left + mScanCornerSize, top + mScanCornerLength, mPaint);

                canvas.drawRect(right - mScanCornerLength, top, right, top + mScanCornerSize, mPaint);
                canvas.drawRect(right - mScanCornerSize, top, right, top + mScanCornerLength, mPaint);

                canvas.drawRect(left, bottom - mScanCornerLength, left + mScanCornerSize, bottom, mPaint);
                canvas.drawRect(left, bottom - mScanCornerSize, left + mScanCornerLength, bottom, mPaint);

                canvas.drawRect(right - mScanCornerSize, bottom - mScanCornerLength, right, bottom, mPaint);
                canvas.drawRect(right - mScanCornerLength, bottom - mScanCornerSize, right, bottom, mPaint);
            } else if (mScanCornerGravity == CORNER_OUTSIDE) {
                canvas.drawRect(left - mScanCornerSize, top - mScanCornerSize, left + mScanCornerLength - mScanCornerSize, top, mPaint);
                canvas.drawRect(left - mScanCornerSize, top - mScanCornerSize, left, top + mScanCornerLength - mScanCornerSize, mPaint);

                canvas.drawRect(right - mScanCornerLength + mScanCornerSize, top - mScanCornerSize, right + mScanCornerSize, top, mPaint);
                canvas.drawRect(right, top - mScanCornerSize, right + mScanCornerSize, top + mScanCornerLength - mScanCornerSize, mPaint);

                canvas.drawRect(left - mScanCornerSize, bottom - mScanCornerLength + mScanCornerSize, left, bottom + mScanCornerSize, mPaint);
                canvas.drawRect(left - mScanCornerSize, bottom, left + mScanCornerLength - mScanCornerSize, bottom + mScanCornerSize, mPaint);

                canvas.drawRect(right, bottom - mScanCornerLength + mScanCornerSize, right + mScanCornerSize, bottom + mScanCornerSize, mPaint);
                canvas.drawRect(right - mScanCornerLength + mScanCornerSize, bottom, right + mScanCornerSize, bottom + mScanCornerSize, mPaint);
            } else {
                float canCornerSize = mScanCornerSize / 2;
                canvas.drawRect(left - canCornerSize, top - canCornerSize, left + mScanCornerLength - canCornerSize, top + canCornerSize, mPaint);
                canvas.drawRect(left - canCornerSize, top - canCornerSize, left + canCornerSize, top + mScanCornerLength - canCornerSize, mPaint);

                canvas.drawRect(right - mScanCornerLength + canCornerSize, top - canCornerSize, right + canCornerSize, top + canCornerSize, mPaint);
                canvas.drawRect(right - canCornerSize, top - canCornerSize, right + canCornerSize, top + mScanCornerLength - canCornerSize, mPaint);

                canvas.drawRect(left - canCornerSize, bottom - mScanCornerLength + canCornerSize, left + canCornerSize, bottom + canCornerSize, mPaint);
                canvas.drawRect(left - canCornerSize, bottom - canCornerSize, left + mScanCornerLength - canCornerSize, bottom + canCornerSize, mPaint);

                canvas.drawRect(right - canCornerSize, bottom - mScanCornerLength + canCornerSize, right + canCornerSize, bottom + canCornerSize, mPaint);
                canvas.drawRect(right - mScanCornerLength + canCornerSize, bottom - canCornerSize, right + canCornerSize, bottom + canCornerSize, mPaint);
            }
        }
    }

    /**
     * 绘制提示文字及背景
     *
     * @param canvas
     * @param left   扫描框左边坐标
     * @param top    扫描框顶部坐标
     * @param right  扫描框右边坐标
     * @param bottom 扫描框底部坐标
     */
    private void drawTip(Canvas canvas, float left, float top, float right, float bottom) {
        if (mTipColor == Color.TRANSPARENT || mTipSize <= 0 || mTipText == null || mTipText.trim().length() <= 0) {
            return;
        }
        if (mTipGravity == TIP_TOP) {
            mPaint.setTextSize(mTipSize);
            int textLength = mTipText.length();
            Rect bounds = new Rect();
            mPaint.getTextBounds(mTipText, 0, textLength, bounds);
            if (mTipBackgroundColor != Color.TRANSPARENT) {
                Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
                float textTop = getTextTop(fontMetrics);
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setColor(mTipBackgroundColor);
                int width = bounds.width();
                int height = bounds.height();
                float bgLeft = (mWidth - width - mTipPaddingLeft - mTipPaddingRight) / 2;
                float bgRight = bgLeft + width + mTipPaddingLeft + mTipPaddingRight;
                float bgTop = top - mTipMargin - height - mTipPaddingTop - mTipPaddingBottom;
                if (mTipSysText != null && mTipSysText.trim().length() > 0) {
                    bgTop -= (height + mLineSpacing);
                    bgTop -= dp2px(2);
                }
                RectF rectF = new RectF(bgLeft, bgTop, bgRight, top - mTipMargin);
                canvas.drawRoundRect(rectF, mTipBackgroundRadius, mTipBackgroundRadius, mPaint);
                float x = (mWidth - mTipPaddingLeft - mTipPaddingRight) / 2 + mTipPaddingLeft;
                float y = bgTop + mTipPaddingTop + height - textTop;
                drawText(canvas, x, y, mTipText);
                drawText(canvas, x, y + height + mLineSpacing, mTipSysText);
            } else {
                float x = mWidth / 2;
                int height = bounds.height();
                float y = top - mTipMargin;
                if (mTipSysText != null && mTipSysText.trim().length() > 0) {
                    y -= (height + mLineSpacing);
                }
                drawText(canvas, x, y, mTipText);
                drawText(canvas, x, y + height + mLineSpacing, mTipSysText);
            }
        } else {
            mPaint.setTextSize(mTipSize);
            int textLength = mTipText.length();
            Rect bounds = new Rect();
            mPaint.getTextBounds(mTipText, 0, textLength, bounds);
            if (mTipBackgroundColor != Color.TRANSPARENT) {
                Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
                float textTop = getTextTop(fontMetrics);
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setColor(mTipBackgroundColor);
                int width = bounds.width();
                float height = bounds.height();
                float bgLeft = (mWidth - width - mTipPaddingLeft - mTipPaddingRight) / 2;
                float bgRight = bgLeft + width + mTipPaddingLeft + mTipPaddingRight;
                float bgBottom = bottom + mTipMargin + height + mTipPaddingTop + mTipPaddingBottom;
                if (mTipSysText != null && mTipSysText.trim().length() > 0) {
                    bgBottom += (height + mLineSpacing);
                    bgBottom += dp2px(2);
                }
                RectF rectF = new RectF(bgLeft, bottom + mTipMargin, bgRight, bgBottom);
                canvas.drawRoundRect(rectF, mTipBackgroundRadius, mTipBackgroundRadius, mPaint);
                float x = (mWidth - mTipPaddingLeft - mTipPaddingRight) / 2 + mTipPaddingLeft;
                float y = bottom + mTipMargin + mTipPaddingTop + height - textTop;
                drawText(canvas, x, y, mTipText);
                drawText(canvas, x, y + height + mLineSpacing, mTipSysText);
            } else {
                float x = mWidth / 2;
                int height = bounds.height();
                float y = bottom + mTipMargin + height;
                drawText(canvas, x, y, mTipText);
                drawText(canvas, x, y + height + mLineSpacing, mTipSysText);
            }
        }
    }

    /**
     * 获取文字顶部空隙
     *
     * @param fontMetrics
     * @return
     */
    private float getTextTop(Paint.FontMetrics fontMetrics) {
        return fontMetrics.ascent - fontMetrics.top;
    }

    /**
     * 绘制文字
     *
     * @param canvas
     * @param x      x坐标
     * @param y      y坐标
     * @param text   绘制内容
     */
    private void drawText(Canvas canvas, float x, float y, String text) {
        if (text == null || text.trim().length() <= 0) {
            return;
        }
        mPaint.setColor(mTipColor);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(text, 0, text.length(), x, y, mPaint);
    }

    /**
     * 绘制扫描线
     *
     * @param canvas
     * @param left   扫描框左边坐标
     * @param top    扫描框顶部坐标
     * @param right  扫描框右边坐标
     * @param bottom 扫描框底部坐标
     */
    private void drawScanLine(Canvas canvas, float left, float top, float right, float bottom) {
        float start = (mHeight - mScanLineMoveDistance) / 2;
        if (isGridScanLine) {
            if (mGridScanLineBitmap != null) {
                float lineLeft = (mWidth - mScanLineWidth) / 2;
                int height = mGridScanLineBitmap.getHeight();
                float showHeight = mScanLineTop;
                float diff = 0;
                if (showHeight >= height) {
                    showHeight = height;
                    diff = mScanLineTop - showHeight;
                }
                mGridSrcRect.set(0, (int) (height - showHeight), (int) mScanLineWidth, height);
                mGridRect.set((int) lineLeft, (int) (start + diff), (int) (lineLeft + mScanLineWidth), (int) (start + mScanLineTop));
                canvas.drawBitmap(mGridScanLineBitmap, mGridSrcRect, mGridRect, null);
            }
        } else {
            if (mScanLineBitmap != null) {
                float lineLeft = (mWidth - mScanLineWidth) / 2;
                canvas.drawBitmap(mScanLineBitmap, lineLeft, start + mScanLineTop, null);
            }
        }
    }

    /**
     * 移动扫描线
     *
     * @param canvas
     * @param left   扫描框左边坐标
     * @param top    扫描框顶部坐标
     * @param right  扫描框右边坐标
     * @param bottom 扫描框底部坐标
     */
    private void moveScanLine(Canvas canvas, float left, float top, float right, float bottom) {
        float start = (mHeight - mScanLineMoveDistance) / 2;
        if (isGridScanLine) {
            if (mGridScanLineBitmap != null) {
                mScanLineTop += mMoveStepDistance;
                if (mScanLineTop > mScanLineMoveDistance) {
                    mScanLineTop = 0;
                }
                float lineLeft = (mWidth - mScanLineWidth) / 2;
                postInvalidateDelayed(mAnimDelayTime, (int) lineLeft, (int) start, (int) (lineLeft + mScanLineWidth), (int) (start + mScanLineMoveDistance));
            }
        } else {
            if (mScanLineBitmap != null) {
                mScanLineTop += mMoveStepDistance;
                int height = mScanLineBitmap.getHeight();
                if (mScanLineTop > mScanLineMoveDistance - height) {
                    mScanLineTop = 0;
                }
                float lineLeft = (mWidth - mScanLineWidth) / 2;
                postInvalidateDelayed(mAnimDelayTime, (int) lineLeft, (int) start, (int) (lineLeft + mScanLineWidth), (int) (start + mScanLineMoveDistance));
            }
        }
    }

    /**
     * dp转px
     *
     * @param dp
     * @return
     */
    private float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    /**
     * sp转px
     *
     * @param sp
     * @return
     */
    private float sp2px(float sp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }

    /**
     * 设置扫描框宽度
     *
     * @param width dp
     * @return
     */
    public ScanBoxView setScanWidth(float width) {
        this.mScanWidth = dp2px(width);
        return this;
    }

    /**
     * 设置扫描框宽度
     *
     * @param width px
     * @return
     */
    public ScanBoxView setScanWidth(int width) {
        this.mScanWidth = width;
        return this;
    }

    /**
     * 设置扫描框高度
     *
     * @param height dp
     * @return
     */
    public ScanBoxView setScanHeight(float height) {
        this.mScanHeight = height;
        return this;
    }

    /**
     * 设置扫描框高度
     *
     * @param height px
     * @return
     */
    public ScanBoxView setScanHeight(int height) {
        this.mScanHeight = height;
        return this;
    }

    /**
     * 设置扫描框位置
     *
     * @param gravity
     * @return
     */
    public ScanBoxView setScanGravity(@ScanGravity int gravity) {
        this.mScanGravity = gravity;
        return this;
    }

    /**
     * 扫描框偏移（正数：向下偏移，负数：向上偏移）
     *
     * @param offset dp
     * @return
     */
    public ScanBoxView setScanOffset(float offset) {
        this.mScanOffset = dp2px(offset);
        return this;
    }

    /**
     * 扫描框偏移（正数：向下偏移，负数：向上偏移）
     *
     * @param offset px
     * @return
     */
    public ScanBoxView setScanOffset(int offset) {
        this.mScanOffset = offset;
        return this;
    }

    /**
     * 扫描框边框大小
     *
     * @param size dp
     * @return
     */
    public ScanBoxView setScanBorderSize(float size) {
        this.mScanBorderSize = dp2px(size);
        return this;
    }

    /**
     * 扫描框边框大小
     *
     * @param size px
     * @return
     */
    public ScanBoxView setScanBorderSize(int size) {
        this.mScanBorderSize = size;
        return this;
    }

    /**
     * 扫描框四角边框宽度
     *
     * @param size dp
     * @return
     */
    public ScanBoxView setScanCornerSize(float size) {
        this.mScanCornerSize = dp2px(size);
        return this;
    }

    /**
     * 扫描框四角边框宽度
     *
     * @param size px
     * @return
     */
    public ScanBoxView setScanCornerSize(int size) {
        this.mScanCornerSize = size;
        return this;
    }

    /**
     * 扫描框四角边框长度
     *
     * @param length dp
     * @return
     */
    public ScanBoxView setScanCornerLength(float length) {
        this.mScanCornerLength = dp2px(length);
        return this;
    }

    /**
     * 扫描框四角边框长度
     *
     * @param length px
     * @return
     */
    public ScanBoxView setScanCornerLength(int length) {
        this.mScanCornerLength = length;
        return this;
    }

    /**
     * 扫描框边框颜色
     *
     * @param color
     * @return
     */
    public ScanBoxView setScanBorderColor(int color) {
        this.mScanBorderColor = color;
        return this;
    }

    /**
     * 扫描框四角边框颜色
     *
     * @param color
     * @return
     */
    public ScanBoxView setScanCornerColor(int color) {
        this.mScanCornerColor = color;
        return this;
    }

    /**
     * 扫描框四角边框位置
     *
     * @param gravity
     * @return
     */
    public ScanBoxView setScanCornerGravity(@CornerGravity int gravity) {
        this.mScanCornerGravity = gravity;
        return this;
    }

    /**
     * 遮罩颜色
     *
     * @param color
     * @return
     */
    public ScanBoxView setMaskColor(int color) {
        this.mMaskColor = color;
        return this;
    }

    /**
     * 提示文字内容
     *
     * @param text
     * @return
     */
    public ScanBoxView setTipText(String text) {
        this.mTipText = text;
        return this;
    }

    /**
     * 系统提示文字
     *
     * @param text
     * @return
     */
    public ScanBoxView setTipSysText(String text) {
        this.mTipSysText = text;
        return this;
    }

    /**
     * 提示文字和系统文字间距
     *
     * @param lineSpacing dp
     * @return
     */
    public ScanBoxView setLineSpacing(float lineSpacing) {
        this.mLineSpacing = dp2px(lineSpacing);
        return this;
    }

    /**
     * 提示文字和系统文字间距
     *
     * @param lineSpacing px
     * @return
     */
    public ScanBoxView setLineSpacing(int lineSpacing) {
        this.mLineSpacing = lineSpacing;
        return this;
    }

    /**
     * 文字大小
     *
     * @param size sp
     * @return
     */
    public ScanBoxView setTipSize(float size) {
        this.mTipSize = sp2px(size);
        return this;
    }

    /**
     * 文字大小
     *
     * @param size px
     * @return
     */
    public ScanBoxView setTipSize(int size) {
        this.mTipSize = size;
        return this;
    }

    /**
     * 文字位置
     *
     * @param gravity
     * @return
     */
    public ScanBoxView setTipGravity(@TipGravity int gravity) {
        this.mTipGravity = gravity;
        return this;
    }

    /**
     * 文字与扫描框的间距
     *
     * @param margin dp
     * @return
     */
    public ScanBoxView setTipMargin(float margin) {
        this.mTipMargin = dp2px(margin);
        return this;
    }

    /**
     * 文字与扫描框的间距
     *
     * @param margin px
     * @return
     */
    public ScanBoxView setTipMargin(int margin) {
        this.mTipMargin = margin;
        return this;
    }

    /**
     * 提示文字颜色
     *
     * @param color
     * @return
     */
    public ScanBoxView setTipColor(int color) {
        this.mTipColor = color;
        return this;
    }

    /**
     * 提示文字背景框颜色
     *
     * @param color
     * @return
     */
    public ScanBoxView setTipBackgroundColor(int color) {
        this.mTipBackgroundColor = color;
        return this;
    }

    /**
     * 提示文字背景框圆角
     *
     * @param radius dp
     * @return
     */
    public ScanBoxView setTipBackgroundRadius(float radius) {
        this.mTipBackgroundRadius = dp2px(radius);
        return this;
    }

    /**
     * 提示文字背景框圆角
     *
     * @param radius px
     * @return
     */
    public ScanBoxView setTipBackgroundRadius(int radius) {
        this.mTipBackgroundRadius = radius;
        return this;
    }

    /**
     * 提示文字背景框左内间距
     *
     * @param paddingLeft dp
     * @return
     */
    public ScanBoxView setTipPaddingLeft(float paddingLeft) {
        this.mTipPaddingLeft = dp2px(paddingLeft);
        return this;
    }

    /**
     * 提示文字背景框左内间距
     *
     * @param paddingLeft px
     * @return
     */
    public ScanBoxView setTipPaddingLeft(int paddingLeft) {
        this.mTipPaddingLeft = paddingLeft;
        return this;
    }

    /**
     * 提示文字背景框右内间距
     *
     * @param paddingRight dp
     * @return
     */
    public ScanBoxView setTipPaddingRight(float paddingRight) {
        this.mTipPaddingRight = dp2px(paddingRight);
        return this;
    }

    /**
     * 提示文字背景框右内间距
     *
     * @param paddingRight px
     * @return
     */
    public ScanBoxView setTipPaddingRight(int paddingRight) {
        this.mTipPaddingRight = paddingRight;
        return this;
    }

    /**
     * 提示文字背景框顶部内间距
     *
     * @param paddingTop dp
     * @return
     */
    public ScanBoxView setTipPaddingTop(float paddingTop) {
        this.mTipPaddingTop = dp2px(paddingTop);
        return this;
    }

    /**
     * 提示文字背景框顶部内间距
     *
     * @param paddingTop px
     * @return
     */
    public ScanBoxView setTipPaddingTop(int paddingTop) {
        this.mTipPaddingTop = paddingTop;
        return this;
    }

    /**
     * 提示文字背景框底部内间距
     *
     * @param paddingBottom dp
     * @return
     */
    public ScanBoxView setTipPaddingBottom(float paddingBottom) {
        this.mTipPaddingBottom = dp2px(paddingBottom);
        return this;
    }

    /**
     * 提示文字背景框底部内间距
     *
     * @param paddingBottom px
     * @return
     */
    public ScanBoxView setTipPaddingBottom(int paddingBottom) {
        this.mTipPaddingBottom = paddingBottom;
        return this;
    }

    /**
     * 提示文字背景框内间距
     *
     * @param left   dp
     * @param top    dp
     * @param right  dp
     * @param bottom dp
     * @return
     */
    public ScanBoxView setTipPadding(float left, float top, float right, float bottom) {
        this.mTipPaddingLeft = dp2px(left);
        this.mTipPaddingTop = dp2px(top);
        this.mTipPaddingRight = dp2px(right);
        this.mTipPaddingBottom = dp2px(bottom);
        return this;
    }

    /**
     * 提示文字背景框内间距
     *
     * @param left   px
     * @param top    px
     * @param right  px
     * @param bottom px
     * @return
     */
    public ScanBoxView setTipPadding(int left, int top, int right, int bottom) {
        this.mTipPaddingLeft = left;
        this.mTipPaddingTop = top;
        this.mTipPaddingRight = right;
        this.mTipPaddingBottom = bottom;
        return this;
    }

    /**
     * 设置扫描线资源
     *
     * @param resId
     * @return
     */
    public ScanBoxView setScanLineResId(int resId) {
        this.mScanLineResId = resId;
        if (!isGridScanLine) {
            if (mScanLineBitmap != null) {
                mScanLineBitmap.recycle();
                mScanLineBitmap = null;
            }
            if (mScanLineBitmap == null) {
                mScanLineBitmap = createBitmap(mScanLineResId, mScanLineWidth);
            }
        }
        return this;
    }

    /**
     * 设置网格扫描线资源
     *
     * @param resId
     * @return
     */
    public ScanBoxView setGridScanLineResId(int resId) {
        this.mGridScanLineResId = resId;
        if (isGridScanLine) {
            if (mGridScanLineBitmap != null) {
                mGridScanLineBitmap.recycle();
                mGridScanLineBitmap = null;
            }
            if (mGridScanLineBitmap == null) {
                mGridScanLineBitmap = createBitmap(mGridScanLineResId, mScanLineWidth);
            }
        }
        return this;
    }

    /**
     * 设置是否使用网格扫描线
     *
     * @param gridScanLine true 使用，false 不使用
     * @return
     */
    public ScanBoxView setGridScanLine(boolean gridScanLine) {
        isGridScanLine = gridScanLine;
        createBitmap();
        return this;
    }

    /**
     * 动画执行时间
     *
     * @param animTime
     * @return
     */
    public ScanBoxView setAnimTime(long animTime) {
        this.mAnimTime = animTime;
        countAnimTime();
        return this;
    }
}
